﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SearchOperations.DAL
{
    public class BookOperations
    {
        private static ProbaTravelDataContext cont;

        static BookOperations()
        {
            cont = new ProbaTravelDataContext();
        }

        public static List<Settlement> GetAllSettlements(bool insertEmpty)
        {
            var settlements = from s in cont.Settlements
                              select s;

            List<Settlement> result = settlements.ToList();

            if (insertEmpty)
            {
                Settlement se = new Settlement();
                se.SettlementID = 0;
                se.SettlementName = string.Empty;
                result.Insert(0, se);

            }
            return result;
        }

        public static List<Settlement> GetAllConnectedSettlements(long settlementID)
        {
            List<WayStation> allWayStations = DijkstraSearching.GetAllWayStationForSettlement(settlementID);

            List<WayStation> list = new List<WayStation>();

            foreach (WayStation w in allWayStations)
            {
                var wayList = from way in cont.WayStations
                              where way.DestinationID == w.DestinationID
                              & way.WayStationOrderNumber > w.WayStationOrderNumber
                              select way;

                foreach (WayStation wayS in wayList.ToList())
                {
                    list.Add(wayS);
                }
            }
            List<Settlement> result = new List<Settlement>();
            foreach (WayStation w in list)
            {
                result.Add(GetSettlementByID(w.SettlementID));
            }

            return result;
        }

        private static Settlement GetSettlementByID(long settlementID)
        {
            var sett = (from s in cont.Settlements
                        where s.SettlementID == settlementID
                        select s).First();

            return sett;
        }

        public static List<SimpleRoute> GetRoutes(long start, long end, DateTime date)
        {
            List<long> startDestination = DijkstraSearching.GetAllDestination(start);
            List<long> endDestination = DijkstraSearching.GetAllDestination(end);
            List<long> common = startDestination.Intersect(endDestination).ToList();
            List<SimpleRoute> result = new List<SimpleRoute>();

            foreach (long destID in common)
            {
                string destinationName = GetDestinationName(destID);
                RoadSegment rSegmeent = GetRoadSegment(destID, start, end);
                List<DateTime> startHours = DijkstraSearching.GetStartHours(rSegmeent, date);
                string wayStationNames = GetWayStationNames(rSegmeent, destID);
                decimal price = DijkstraSearching.GetPriceForRoadSegment(rSegmeent);

                foreach (DateTime d in startHours)
                {
                    SimpleRoute route = new SimpleRoute();
                    int hour = d.Hour;
                    int minutes = d.Minute;
                    string hString;
                    string mString;

                    if (hour < 10)
                    {
                        hString = "0" + hour.ToString();
                    }
                    else
                    {
                        hString = hour.ToString();
                    }

                    if (minutes < 10)
                    {
                        mString = "0" + minutes.ToString();
                    }
                    else
                    {
                        mString = minutes.ToString();
                    }

                    string startHour = hString + "," + mString;

                    route.DestinationName = destinationName;
                    route.Price = string.Format("{0:0.00}", price);
                    route.StartHour = startHour;
                    route.WaysStationNames = wayStationNames;

                    result.Add(route);
                }
            }

            return result;
        }

        private static string GetWayStationNames(RoadSegment rSegmeent, long destinationID)
        {
            int startNumber = DijkstraSearching.GetCurrentOrderNumber(rSegmeent.StartWayStationID);
            int endNumber = DijkstraSearching.GetCurrentOrderNumber(rSegmeent.EndWayStationID);

            var wayStationList = from w in cont.WayStations
                                 join d in cont.Destinations
                                 on w.DestinationID equals d.DestinationID
                                 join s in cont.Settlements
                                 on w.SettlementID equals s.SettlementID
                                 where d.DestinationID == destinationID &
                                 w.WayStationOrderNumber >= startNumber &
                                 w.WayStationOrderNumber <= endNumber
                                 orderby w.WayStationOrderNumber
                                 select s.SettlementName;

            StringBuilder wayNames = new StringBuilder();

            foreach (string s in wayStationList.ToList())
            {
                wayNames.Append(s);
                wayNames.Append("-");
            }

            wayNames.Remove(wayNames.Length - 1, 1);

            return wayNames.ToString();
        }

        private static RoadSegment GetRoadSegment(long destID, long start, long end)
        {
            var wayStartID = (from w in cont.WayStations
                              join d in cont.Destinations
                              on w.DestinationID equals d.DestinationID
                              where w.SettlementID == start & w.DestinationID == destID
                              select w.WayStationID).First();

            var wayEndID = (from w in cont.WayStations
                            join d in cont.Destinations
                            on w.DestinationID equals d.DestinationID
                            where w.SettlementID == end & w.DestinationID == destID
                            select w.WayStationID).First();

            RoadSegment road = new RoadSegment();
            road.StartWayStationID = wayStartID;
            road.EndWayStationID = wayEndID;

            return road;
        }

        private static string GetDestinationName(long destinationID)
        {
            var name = (from d in cont.Destinations
                        where d.DestinationID == destinationID
                        select d.DestistionName).First();

            return name;
        }

        public static List<RouteDetails> GetRouteDetails(string destinationName, string startID, string endID)
        {
            long start = long.Parse(startID);
            long end = long.Parse(endID);

            return GetRouteDetails(destinationName, start, end);
        }

        public static List<RouteDetails> GetRouteDetails(string destinationName, long startID, long endID)
        {
            int timeSum = 0;
            int staySum = 0;
            decimal priceSum = (decimal)0;

            long destID = (from d in cont.Destinations
                           where d.DestistionName == destinationName
                           select d.DestinationID).First();

            List<RouteDetails> rdList = new List<RouteDetails>();

            int startOrder = DijkstraSearching.GetCurrentOrderNumber(destID, startID);
            int endOrder = DijkstraSearching.GetCurrentOrderNumber(destID, endID);

            RouteDetails rDetails = new RouteDetails();

            rDetails.Name = (from w in cont.WayStations
                             join s in cont.Settlements
                             on w.SettlementID equals s.SettlementID
                             where w.WayStationOrderNumber == startOrder
                             & w.DestinationID == destID
                             select s.SettlementName).First();

            rDetails.Price = "---";
            rDetails.Time = "---";
            rDetails.Stay = "---";

            rdList.Add(rDetails);

            for (int i = startOrder + 1; i <= endOrder; i++)
            {
                RouteDetails nextRouteDetails = new RouteDetails();

                nextRouteDetails.Name = (from w in cont.WayStations
                                         join s in cont.Settlements
                                         on w.SettlementID equals s.SettlementID
                                         where w.WayStationOrderNumber == i
                                         & w.DestinationID == destID
                                         select s.SettlementName).First();

                int time = (from w in cont.WayStations
                            join s in cont.Settlements
                            on w.SettlementID equals s.SettlementID
                            where w.WayStationOrderNumber == i
                            select w.WayStationTimeTo).First();

                timeSum += time;
                nextRouteDetails.Time = string.Format("{0},{1:00}", time / 60, time % 60);

                if (i != endOrder)
                {
                    int stay = (from w in cont.WayStations
                                join s in cont.Settlements
                                on w.SettlementID equals s.SettlementID
                                where w.WayStationOrderNumber == i
                                select w.WayStationStay).First();

                    staySum += stay;
                    nextRouteDetails.Stay = string.Format("0,{0:00}", stay);
                }
                else
                {
                    nextRouteDetails.Stay = "---";
                }


                decimal price = (from w in cont.WayStations
                                 join s in cont.Settlements
                                 on w.SettlementID equals s.SettlementID
                                 where w.WayStationOrderNumber == i
                                 select w.WayStationPriceTo).First();

                priceSum += price;

                nextRouteDetails.Price = string.Format("{0:0.00}", price);

                rdList.Add(nextRouteDetails);
            }

            RouteDetails total = new RouteDetails();

            total.Name = "Total";
            total.Time = string.Format("{0},{1:00}", (timeSum + staySum) / 60, (timeSum + staySum) % 60);
            total.Price = string.Format("{0:0.00}", priceSum);
            total.Stay = "---";

            rdList.Add(total);

            return rdList;
        }

        public static string ComplexBook(string travelDetails, int passengers, string username)
        {
            string result = "";

            string[] details = travelDetails.Split(';');
            TimeSpan ts = new TimeSpan(0, 30, 0);
            string[] segmentDetails = details[0].Split(',');
            DateTime date = DateTime.Parse(segmentDetails[0]);

            if ((date - DateTime.Now) < ts)
            {
                return GetMessageString(1, 0);
            }

            List<string[]> travelList = new List<string[]>();

            foreach (string str in details)
            {
                if (str != string.Empty)
                {
                    string[] segment = str.Split(',');
                    travelList.Add(segment);
                }
            }

            int vacant = GetVacant(travelList, passengers);

            if (vacant == -1)
            {
                result = GetMessageString(2, 0);
            }
            else if (vacant == 0)
            {
                result = GetMessageString(4, 0);
            }
            else if (vacant > 0 && vacant < int.MaxValue)
            {
                result = GetMessageString(3, vacant);
            }
            else
            {
                result = GetMessageString(5, 0);

                foreach (string[] trL in travelList)
                {
                    DateTime tempDate = DateTime.Parse(trL[0]);
                    long destID = DijkstraSearching.GetDestinationIDForWayStation(long.Parse(trL[1]));
                    int startN = DijkstraSearching.GetCurrentOrderNumber(long.Parse(trL[1]));
                    int endN = DijkstraSearching.GetCurrentOrderNumber(long.Parse(trL[2]));
                    int startHour = tempDate.Hour * 60 + tempDate.Minute;

                    long[] bookInfo = new long[11];

                    bookInfo[2] = tempDate.Year;
                    bookInfo[3] = tempDate.Month;
                    bookInfo[4] = tempDate.Day;
                    bookInfo[5] = passengers;
                    bookInfo[6] = startHour;
                    bookInfo[7] = GetUserID(username);
                    bookInfo[8] = destID;
                    bookInfo[9] = startN;
                    bookInfo[10] = endN;

                    Booking(bookInfo);
                }

            }

            return result;
        }

        private static int GetVacant(List<string[]> travelList, int passengers)
        {
            int[] vacant = new int[travelList.Count];
            int result = int.MaxValue;

            for (int i = 0; i < vacant.Length; i++)
            {
                vacant[i] = int.MaxValue;
            }

            for (int j = 0; j < travelList.Count; j++)
            {
                DateTime date = DateTime.Parse(travelList.ElementAt(j)[0]);
                long start = long.Parse(travelList.ElementAt(j)[1]);
                long end = long.Parse(travelList.ElementAt(j)[2]);
                long destID = DijkstraSearching.GetDestinationIDForWayStation(start);
                int startN = DijkstraSearching.GetCurrentOrderNumber(start);
                int endN = DijkstraSearching.GetCurrentOrderNumber(end);

                int busCapacity = GetBusCapacity(destID, date, (date.Hour * 60 + date.Minute));

                if (busCapacity == 0)
                {
                    vacant[j] = -1;
                }
                else
                {
                    int booked = 0;
                    int temp = 0;

                    for (int i = startN; i < endN; i++)
                    {
                        temp = GetBookedPlaces(destID, date, (date.Hour * 60 + date.Minute), i);

                        if (temp > booked)
                        {
                            booked = temp;
                        }
                    }

                    if (booked == busCapacity)
                    {
                        vacant[j] = 0;
                    }
                    else if ((busCapacity - booked) < passengers)
                    {
                        vacant[j] = busCapacity - booked;
                    }
                }
            }

            if (vacant.Contains(0))
            {
                result = 0;
            }
            else
            {
                if (vacant.Contains(-1))
                {
                    for (int i = 0; i < vacant.Length; i++)
                    {
                        if (vacant[i] == -1)
                        {
                            vacant[i] = int.MaxValue;
                        }
                    }

                    result = vacant.Min();

                    if (result == int.MaxValue)
                    {
                        result = -1;
                    }
                }
                else
                {
                    result = vacant.Min();
                }
            }

            return result;
        }

        public static string Book(string bookDetails, string userName)
        {
            long[] bookInfo = BookHelp(bookDetails, userName);
            string result = GetMessageString((int)bookInfo[0], (int)bookInfo[1]);

            if (bookInfo[0] == 5)
            {
                Booking(bookInfo);
            }

            return result;
        }

        private static string GetMessageString(int caseInt, int vacant)
        {
            string result = "";

            switch (caseInt)
            {
                case 1:
                    result = "This route is already closed for booking.";
                    break;
                case 2:
                    result = "Currently we do not opearte the requested route. "
                + "Please, try to book later or contact us by e-mail or phone!";
                    break;
                case 3:
                    result = string.Format("We are sorry - there are only {0} vacant palaces on this route.",
                        vacant);
                    break;
                case 4:
                    result = "We are sorry - there are no vacant palaces on this route.";
                    break;
                case 5:
                    result = "You reservation is complete.";
                    break;
            }

            return result;
        }

        private static void Booking(long[] bookInfo)
        {
            decimal singlePrice = DijkstraSearching.GetPriceForRoadSegment(bookInfo[8], (int)bookInfo[9], (int)bookInfo[10]);
            decimal multiPrice = singlePrice * bookInfo[5];
            decimal amount = GetAmountTillNow(bookInfo[7]);
            DateTime date = new DateTime((int)bookInfo[2], (int)bookInfo[3], (int)bookInfo[4]);
            decimal discountAmount = (decimal)0;

            double discountVip = (from us in cont.UserStatuses
                                  where us.UserStatusType == "VIP"
                                  select us.UserDiscount).First();

            double discountAdv = (from us in cont.UserStatuses
                                  where us.UserStatusType == "Advanced"
                                  select us.UserDiscount).First();

            long buShedID = (from bs in cont.BusShedules
                             join s in cont.StartHours
                             on bs.StartHourID equals s.StartHourID
                             where bs.BusSheduleDate.Date == date.Date && s.DestinationID == bookInfo[8] &&
                             s.StartHourValue == (int)bookInfo[6]
                             select bs.BusSheduleID).First();

            decimal advanced = (from us in cont.UserStatuses
                                where us.UserStatusType == "Advanced"
                                select us.UserStatusAmount).First();

            decimal vip = (from us in cont.UserStatuses
                           where us.UserStatusType == "VIP"
                           select us.UserStatusAmount).First();

            if (amount > vip)
            {
                discountAmount = multiPrice * (decimal)discountVip;
            }
            else if (amount <= vip & amount > advanced)
            {
                if ((amount + multiPrice * ((decimal)1 - (decimal)discountAdv)) <= vip)
                {
                    discountAmount = multiPrice * (decimal)discountAdv;
                }
                else
                {
                    decimal temp = (vip - amount) / ((decimal)1 - (decimal)discountAdv);

                    discountAmount = temp - vip + amount + (multiPrice - temp) * (decimal)discountVip;
                }
            }
            else
            {
                if (amount + multiPrice <= advanced)
                {
                    discountAmount = 0;
                }
                else if (((multiPrice - advanced + amount) * ((decimal)1 - (decimal)discountAdv) + advanced) < vip)
                {
                    discountAmount = (multiPrice - advanced + amount) * (decimal)discountAdv;
                }
                else
                {
                    decimal tempDisc = ((vip - advanced) * (decimal)discountAdv) / ((decimal)1 - (decimal)discountAdv);

                    discountAmount = tempDisc + (multiPrice - vip - tempDisc + amount) * (decimal)discountVip;
                }
            }

            Reservation res = new Reservation();

            res.BusSheduleID = buShedID;
            res.ReservationCount = (int)bookInfo[5];
            res.UserID = bookInfo[7];
            res.ReservationStatusID = 2;
            res.ReservationPrice = multiPrice;
            res.ReservationDiscount = discountAmount;
            res.ReservationStartPoint = (int)bookInfo[9];
            res.ReservationEndPoint = (int)bookInfo[10];

            cont.Reservations.InsertOnSubmit(res);
            cont.SubmitChanges();

        }

        private static decimal GetAmountTillNow(long userID)
        {
            decimal amountDiscount;
            decimal amountPrice;
            decimal amountTotal;

            var count = from r in cont.Reservations
                        where r.UserID == userID
                        select r;



            if (count.ToList().Count() != 0)
            {
                amountPrice = (from r in cont.Reservations
                               where r.UserID == userID
                               select r.ReservationPrice).Sum();
            }
            else
            {
                amountPrice = (decimal)0;
            }



            if (count.ToList().Count() != 0)
            {
                amountDiscount = (from r in cont.Reservations
                                  where r.UserID == userID
                                  select r.ReservationPrice).Sum();
            }
            else
            {
                amountDiscount = (decimal)0;
            }

            amountTotal = amountPrice - amountDiscount;

            return amountTotal;
        }

        private static long[] BookHelp(string bookDetails, string userName)
        {
            string[] details = bookDetails.Split(';');
            string[] startHourE = details[1].Split(',');

            long[] result = new long[11];

            long destID = GetDestIDByName(details[0]);

            int startN = DijkstraSearching.GetCurrentOrderNumber(destID, int.Parse(details[2]));
            int endN = DijkstraSearching.GetCurrentOrderNumber(destID, int.Parse(details[3]));
            int busCapacity;
            string[] dateA = details[4].Split('/');
            int[] bookedPlaces = new int[endN - startN];
            DateTime date = new DateTime(int.Parse(dateA[2]), int.Parse(dateA[0]), int.Parse(dateA[1]));
            int startHour = int.Parse(startHourE[0]) * 60 + int.Parse(startHourE[1]);

            TimeSpan lessTS = new TimeSpan(0, 0, 30, 0);

            if ((date.Date.AddMinutes(startHour) - DateTime.Now) < lessTS)
            {
                result[0] = 1;
                return result;
            }

            for (int i = startN; i < endN; i++)
            {
                bookedPlaces[i - startN] = GetBookedPlaces(destID, date, startHour, i);
            }

            busCapacity = GetBusCapacity(destID, date, startHour);

            if (busCapacity == 0)
            {
                result[0] = 2;
                return result;
            }

            foreach (int i in bookedPlaces)
            {
                int busyMax = 0;

                if (i + int.Parse(details[5]) > busCapacity)
                {
                    if (i > busyMax)
                    {
                        busyMax = i;
                    }
                }

                if (busyMax > 0)
                {
                    if (busyMax < busCapacity)
                    {
                        result[0] = 3;
                        result[1] = busCapacity - busyMax;
                    }
                    else
                    {
                        result[0] = 4;
                    }

                    return result;
                }
            }

            result[0] = 5;
            result[2] = date.Year;
            result[3] = date.Month;
            result[4] = date.Day;
            result[5] = long.Parse(details[5]);
            result[6] = startHour;
            result[7] = GetUserID(userName);
            result[8] = destID;
            result[9] = startN;
            result[10] = endN;

            return result;
        }

        private static long GetUserID(string userName)
        {
            long userID = (from u in cont.Users
                           where u.UserEmail == userName
                           select u.UserId).First();

            return userID;
        }

        private static long GetDestIDByName(string name)
        {
            long destID = (from d in cont.Destinations
                           where d.DestistionName == name
                           select d.DestinationID).First();

            return destID;
        }

        private static int GetBookedPlaces(long destID, DateTime date, int startHour, int startN)
        {
            int isZero = (from s in cont.StartHours
                          join bs in cont.BusShedules
                          on s.StartHourID equals bs.StartHourID
                          join r in cont.Reservations
                          on bs.BusSheduleID equals r.BusSheduleID
                          where s.DestinationID == destID && bs.BusSheduleDate.Date == date.Date &&
                          s.StartHourValue == startHour && r.ReservationStartPoint <= startN &&
                          r.ReservationEndPoint > startN
                          select r.ReservationCount).Count();

            int count;
            if (isZero != 0)
            {
                count = (from s in cont.StartHours
                         join bs in cont.BusShedules
                         on s.StartHourID equals bs.StartHourID
                         join r in cont.Reservations
                         on bs.BusSheduleID equals r.BusSheduleID
                         where s.DestinationID == destID && bs.BusSheduleDate.Date == date.Date &&
                         s.StartHourValue == startHour && r.ReservationStartPoint <= startN &&
                         r.ReservationEndPoint > startN
                         select r.ReservationCount).Sum();
            }
            else
            {
                count = 0;
            }

            return count;
        }

        private static int GetBusCapacity(long destID, DateTime date, int startHour)
        {
            int count = (from s in cont.StartHours
                         join bs in cont.BusShedules
                         on s.StartHourID equals bs.StartHourID
                         join b in cont.Buses
                         on bs.BusID equals b.BusID
                         where bs.BusSheduleDate.Date == date.Date && s.StartHourValue == startHour &&
                         s.DestinationID == destID
                         select b.BusCapacity).Count();

            int capacity;
            if (count != 0)
            {
                capacity = (from s in cont.StartHours
                            join bs in cont.BusShedules
                            on s.StartHourID equals bs.StartHourID
                            join b in cont.Buses
                            on bs.BusID equals b.BusID
                            where bs.BusSheduleDate.Date == date.Date && s.StartHourValue == startHour &&
                            s.DestinationID == destID
                            select b.BusCapacity).First();
            }
            else
            {
                capacity = 0;
            }

            return capacity;
        }

        public static long GetSettlementID(string settlementName)
        {
            long settID = (from s in cont.Settlements
                           where s.SettlementName == settlementName
                           select s.SettlementID).First();

            return settID;
        }
    }
}